import { ref, onMounted, onBeforeUnmount, watch, nextTick } from "vue";
import type { Ref } from "vue";

interface Config<T> {
  data: Ref<T[]>; // 数据源
  scrollContainer: string; // 滚动容器的元素选择器
  actualHeightContainer: string; // 用于撑开高度的元素选择器
  translateContainer: string; // 用于偏移的元素选择器
  itemContainer: string; // 列表项选择器
  itemHeight: number; // 列表项高度
  size: number; // 每次渲染数据量
  onReachBottom?: (index: number, data: T[]) => void; //触底函数
}

type HtmlElType = HTMLElement | null;

export default function useVirtualList<T>(config: Config<T>) {
  let actualHeightContainerEl: HtmlElType = null;
  let translateContainerEl: HtmlElType = null;
  let scrollContainerEl: HtmlElType = null;

  const dataSource = ref<any[]>([]);
  const actualRenderData = ref<any[]>([]);
  const page = ref(0);
  onMounted(() => {
    dataSource.value = config.data.value ?? [];
    actualHeightContainerEl = document.querySelector(
      config.actualHeightContainer
    );
    scrollContainerEl = document.querySelector(config.scrollContainer);
    translateContainerEl = document.querySelector(config.translateContainer);
    updateRenderData(0);
    scrollContainerEl?.addEventListener("scroll", handleScroll);
  });

  watch(
    () => config.data.value,
    (newVal) => {
      dataSource.value = newVal ?? [];
      updateRenderData(0);
    }
  );

  const updateActualHeight = () => {
    const totalHeight = Object.keys(RenderedItemsCache).reduce(
      (acc, key) => acc + RenderedItemsCache[+key],
      0
    );
    actualHeightContainerEl!.style.height = `${totalHeight}px`;
  };

  const RenderedItemsCache: Record<number, number> = {};

  const updateRenderedItemCache = (startIndex: number) => {
    nextTick(() => {
      const items: HTMLElement[] = Array.from(
        document.querySelectorAll(config.itemContainer)
      );
      items.forEach((el, index) => {
        const idx = startIndex + index;
        if (!(idx in RenderedItemsCache)) {
          RenderedItemsCache[idx] = el.offsetHeight;
        }
      });
      updateActualHeight();
    });
  };

  const getItemHeightFromCache = (index: number) =>
    RenderedItemsCache[index] ?? config.itemHeight;

  const updateRenderData = (scrollTop: number) => {
    let offsetHeight = 0;
    let startIndex = 0;

    for (let i = 0; i < dataSource.value.length; i++) {
      offsetHeight += getItemHeightFromCache(i);
      if (offsetHeight >= scrollTop) {
        startIndex = i;
        page.value = i;
        break;
      }
    }

    actualRenderData.value = dataSource.value.slice(
      startIndex,
      startIndex + config.size
    );
    updateRenderedItemCache(startIndex);
    // 更新偏移值
    updateOffset(offsetHeight - getItemHeightFromCache(startIndex));
  };
  watch(
    () => page.value,
    (newPage) => {
      config?.onReachBottom?.(newPage, actualRenderData.value);
    }
  );
  const updateOffset = (offset: number) => {
    translateContainerEl!.style.transform = `translateY(${offset}px)`;
  };
  const handleScroll = (e: Event) => {
    updateRenderData((e.target as HTMLDivElement).scrollTop);
  };

  onBeforeUnmount(() => {
    scrollContainerEl?.removeEventListener("scroll", handleScroll);
  });

  return {
    actualRenderData,
  };
}
